package org.example.lotterysystem.service.mq;

import cn.hutool.core.date.DateUtil;
import org.example.lotterysystem.common.exception.ServiceExcetion;
import org.example.lotterysystem.common.utils.JacksonUtil;
import org.example.lotterysystem.common.utils.MailUtil;
import org.example.lotterysystem.common.utils.SMSUtil;
import org.example.lotterysystem.controller.param.DrawPrizeParam;
import org.example.lotterysystem.dao.dataobject.ActivityPrizeDo;

import org.example.lotterysystem.dao.dataobject.WinnerRecordDO;
import org.example.lotterysystem.dao.mapper.ActivityPrizeMapper;
import org.example.lotterysystem.dao.mapper.WinnerRecordMapper;
import org.example.lotterysystem.service.DrawPrizeService;

import org.example.lotterysystem.service.activitystatus.ActivityStatusManager;
import org.example.lotterysystem.service.dto.ConvertActivityStatusDTO;
import org.example.lotterysystem.service.enums.ActivityPrizeStatusEnum;
import org.example.lotterysystem.service.enums.ActivityPrizeTiersEnum;
import org.example.lotterysystem.service.enums.ActivityStatusEnum;
import org.example.lotterysystem.service.enums.ActivityUserStatusEnum;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import java.util.stream.Collectors;

import static org.example.lotterysystem.common.config.DirectRabbitConfig.QUEUE_NAME;

@Component
@RabbitListener(queues = QUEUE_NAME)
public class MqReceiver {


    private final  static Logger logger = LoggerFactory.getLogger(MqReceiver.class);

    @Autowired
    private DrawPrizeService drawPrizeService;
    @Autowired
    private ActivityStatusManager activityStatusManager;

    @Qualifier("asyncServiceExecutor")
    @Autowired
    private ThreadPoolTaskExecutor threadPoolTaskExecutor;

    @Autowired
    private MailUtil mailUtil;

    @Autowired
    private SMSUtil smsUtil;

    @Autowired
    private ActivityPrizeMapper activityPrizeMapper;

    @Autowired
    private WinnerRecordMapper winnerRecordMapper;


    @RabbitHandler
    public void process(Map<String,String> message){
        //成功接收到队列中的消息
        logger.info("mq成功接收到消息:{}",
                JacksonUtil.writeValueAsString(message));
        String paramString = message.get("messageData");
        DrawPrizeParam param = JacksonUtil.readValue(paramString, DrawPrizeParam.class);
        //处理抽奖流程
       try{
           //校验抽奖请求是否有效
           //1、有可能前端发起两个一样的抽奖请求，对于param来说也是一样的两个请求
           //2、param：最后一个奖项-》
           //   处理param1：活动完成、奖品完成
           //   处理param2：回滚活动、奖品状态

           if(!drawPrizeService.checkDrawPrizeParam(param)){
               return;
           }
           //状态扭转处理(重要！！！加入设计模式)
           statusConvert(param);
           //保存中奖者名单
           List<WinnerRecordDO> winnerRecordDOList
                   = drawPrizeService.saveWinnerRecords(param);
           //通知中奖者（邮箱、短信）
            //抽奖之后的后续流程，异步（并发）处理
           syncExcute(winnerRecordDOList);
       }catch (ServiceExcetion e){
           logger.error("处理mq消息异常!:{},{}",e.getCode(),e.getMessage());
           //如果异常，需要保证事务一致性（回滚）,抛出异常
           rollback(param);
           throw e;
       }catch (Exception e){
           logger.error("处理mq消息异常!:",e);
           //如果异常，需要保证事务一致性（回滚）,
           // 抛出异常:消息重试:代码bug，网络问题，服务器问题
           rollback(param);
           throw e;
       }
    }

    /**
     * 处理抽奖异常的回滚行为   恢复处理请求之前的库表状态
     * @param param
     */
    private void rollback(DrawPrizeParam param) {
        //1、回滚状态：活动，奖品、人员
        //状态是否需要回滚
        if(!statusNeedRollback(param)){
            //不需要：return
            return;
        }
        //需要回滚:回滚操作
        rollbackStatus(param);
        //2、回滚中奖者名单
        //是否需要回滚
        if (!winnerNeedRollback(param)){
            //不需要：直接return
            return;
        }
        //需要：回滚操作
        rollbackWinner(param);
    }

    /**
     * 回滚中奖记录：删除奖品下的中奖者
     * @param param
     */
    private void rollbackWinner(DrawPrizeParam param) {
        drawPrizeService.deleteRecords(param.getActivityId(), param.getPrizeId());
    }

    /**
     * 判断获奖人员表是否需要回滚
     * @param param
     * @return
     */
    private boolean winnerNeedRollback(DrawPrizeParam param) {
        //判断活动中的奖品是否存在中奖者
        int count = winnerRecordMapper.countByAPId(param.getActivityId(),param.getPrizeId());
        return count > 0;
    }

    /**
     * 回滚状态扭转
     * @param param
     */
    private void rollbackStatus(DrawPrizeParam param) {
        ConvertActivityStatusDTO convertActivityStatusDTO = new ConvertActivityStatusDTO();
        convertActivityStatusDTO.setActivityId(param.getActivityId());
        convertActivityStatusDTO.setTargetActivityStatus(ActivityStatusEnum.RUNNING);
        convertActivityStatusDTO.setPrizeId(param.getPrizeId());
        convertActivityStatusDTO.setTargetPrizeStatus(ActivityPrizeStatusEnum.INIT);
        convertActivityStatusDTO.setUserIds
                (param.getWinnerList().stream()
                        .map(DrawPrizeParam.Winner::getUserId)
                        .collect(Collectors.toList())
                );
        convertActivityStatusDTO.setTargetUserStatus(ActivityUserStatusEnum.INIT);


        //涉及状态的恢复工作，使用ActivityStatusManager
        activityStatusManager.rollbackHandlerEvent(convertActivityStatusDTO);
    }

    /**
     * 判断状态扭转是否需要回滚
     * @param param
     * @return
     */
    private boolean statusNeedRollback(DrawPrizeParam param) {
        //判断活动+奖品+人员表相关状态是否已经扭转（正常思路）
        // 扭转状态时保证了事务的一致性，要么都扭转了，要么都没扭转（不包含活动）
        //因此只用判断人员或者奖品是否扭转过就能判断出状态是否全部扭转
        //活动可能扭转也可能没扭转（这里需要活动结束才能进行扭转），因此，不能通过活动是否扭转来进行判断
        //结论：判断奖品/人员状态是否扭转就能判断出全部状态是否扭转

        ActivityPrizeDo activityPrizeDo
                = activityPrizeMapper.selectByAPId(param.getActivityId(), param.getPrizeId());
       //已经扭转了，需要回滚
        return activityPrizeDo.getStatus()
                .equalsIgnoreCase(ActivityPrizeStatusEnum.COMPLETED.name());

    }

    /**
     * 并发处理抽奖后续流程
     * @param winnerRecordDOList
     */
    private void syncExcute(List<WinnerRecordDO> winnerRecordDOList) {
        //通过线程池
        //扩展：加入策略或者其他设计模式来完成后续操作

        //短信通知
//        threadPoolTaskExecutor.execute(()->sendMessage(winnerRecordDOList));
        //邮件通知
        threadPoolTaskExecutor.execute(()->sendMail(winnerRecordDOList));
    }

    /**
     * 发邮件
     * @param winnerRecordDOList
     */
    private void sendMail(List<WinnerRecordDO> winnerRecordDOList) {
        if(CollectionUtils.isEmpty(winnerRecordDOList)){
            logger.info("中奖列表为空，不用发邮件！");
            return;
        }
        for (WinnerRecordDO winnerRecordDO:winnerRecordDOList){
          /*  String context = "Hi，" + winningRecordDO.getWinnerName() + "。恭喜你在"
                    + winningRecordDO.getActivityName() + "活动中获得"
                    + ActivityPrizeTiersEnum.forName(winningRecordDO.getPrizeTier()).getMessage()
                    + "：" + winningRecordDO.getPrizeName() + "。获奖时间为"
                    + DateUtil.formatTime(winningRecordDO.getWinningTime()) + "，请尽快领取您的奖励！";*/
            String context = "恭喜你！"+winnerRecordDO.getWinnerName()+"中大奖啦！";
            mailUtil.sendSampleMail(winnerRecordDO.getWinnerEmail(),"中奖通知",context);
        }
    }

    /**
     * 发短信
     * @param winnerRecordDOList
     */
    private void sendMessage(List<WinnerRecordDO> winnerRecordDOList) {
            if(CollectionUtils.isEmpty(winnerRecordDOList)){
                logger.info("中奖列表为空不用发短信");
                return;
            }
            for (WinnerRecordDO winnerRecordDO:winnerRecordDOList){
                Map<String,String> map = new HashMap<>();
                map.put("name",winnerRecordDO.getWinnerName());
                map.put("activityname",winnerRecordDO.getActivityName());
                map.put("prizeTiers", ActivityPrizeTiersEnum.forname(winnerRecordDO.getPrizeTier()).getMessage());
                map.put("prizeName",winnerRecordDO.getPrizeName());
                map.put("winningTime", DateUtil.formatTime(winnerRecordDO.getWinningTime()));

                smsUtil.sendMessage("123456",
                        winnerRecordDO.getWinnerPhoneNumber().getValue(),
                        JacksonUtil.writeValueAsString(map) );
            }
    }

    /**
     * 状态扭转
     * @param param
     */
    private void statusConvert(DrawPrizeParam param) {
        ConvertActivityStatusDTO convertActivityStatusDTO = new ConvertActivityStatusDTO();
        convertActivityStatusDTO.setActivityId(param.getActivityId());
        convertActivityStatusDTO.setTargetActivityStatus(ActivityStatusEnum.COMPLETED);
        convertActivityStatusDTO.setPrizeId(param.getPrizeId());
        convertActivityStatusDTO.setTargetPrizeStatus(ActivityPrizeStatusEnum.COMPLETED);
        convertActivityStatusDTO.setUserIds(param.getWinnerList()
                .stream()
                .map(DrawPrizeParam.Winner::getUserId)
                .collect(Collectors.toList()));
        convertActivityStatusDTO.setTargetUserStatus(ActivityUserStatusEnum.COMPLETED);
        activityStatusManager.handlerEvent(convertActivityStatusDTO);

    }

//    private void statusConvert(DrawPrizeParam param) {
        //问题：
        // 1、活动状态扭转有依赖性，导致代码可维护性差
        // 2、状态扭转条件可能会扩展，当前写法，扩展性差，维护性差
        // 3、代码的灵活性、扩展性、维护性极差
        //解决方案：设计模式（责任链模式、策略模式）

        //活动:RUNNING->COMPLETED     全部奖品抽完之后才改变状态
        //奖品：INIT->COMPLETED
        //人员:INIT->COMPLETED

        //1、扭转奖品状态
        //查询活动关联奖品信息
        //条件判断是否符合扭转奖品状态：判断当前状态是否已完成COMPLETED,如果是，不用扭转
        //才去扭转


        //2、扭转人员状态
        //查询活动关联人员信息
        //条件判断是否符合扭转人员状态：判断当前状态是否已完成COMPLETED,如果是，不用扭转
        //才去扭转

        //3、扭转活动状态（必须在扭转奖品状态之后完成）
        //查询活动信息
        //条件判断是否符合扭转活动状态：判断当前状态是否已完成COMPLETE,
        // 如果是，不用扭转，如果不是，且全部奖品抽完（xxx扭转完）之后才进行扭转
        //才去扭转

        //更新活动完整信息缓存
//    }
}
